Implement Microsoft.PowerShell.SecretManagement extension#1074
Implement Microsoft.PowerShell.SecretManagement extension#1074Gijsreyn wants to merge 4 commits intoPowerShell:mainfrom
Conversation
|
@SteveL-MSFT Testing out the changes, I encountered the fact that most secret vault require some pre-authentication. In this case, it spawns another process and mentions: 2025-08-22T07:28:15.409628Z TRACE dsc_lib::dscresources::command_resource: 935: PID 14876: Get-Secret: C:\source\DSCv3\bin\debug\microsoft.powershell.secret.ps1:22:15 Does it make sense to add an additional argument to be passed along as |
fe1951b to
1d6d6ba
Compare
|
I'm not sure I can think of a clean way to do this from within the existing data model for a configuration document to avoid requiring the user to perform initialization steps and documenting those. Arguably, we could specify something like The following snippet is just an example, not fully thought out: $schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
metadata:
Microsoft.DSC:
extensions: # map where keys must be the extension type
Microsoft.PowerShell/SecretManagement:
enabled: true # default, users can explicitly disable
version: 1.2 # optionally pin to specific version
# Other DSC-specific stuff goes at the top-level of the map,
# so we can do validation/interpretation.
#
# Everything under options comes from the extension, which
# probably has to publish them as a JSON Schema in the manifest.
options: # map of options to pass to the extension
unlockCredential: "[parameters('secretStoreCred')]"This would enable in-document control / options for extensions. If we require extensions to publish their options as a JSON Schema, we can also use this model for incorporating extension options into your DSC settings/policy. I think we probably need to resolve extension options sooner than later, but I wouldn't block this PR on it. Probably, the correct (current) model would be to document that this extension requires you to invoke DSC from a PowerShell session where you have already used the |
|
I filed #1080 for handling extension options separately from this PR. |
5f01d9b to
85cbaf5
Compare
|
Thanks, Mikey, for always providing well-explained and possible solutions to tackle the issue. Let's keep it open for now and see what the others think about it :) |
…into feature/microsoft-powershell-secretmanagement
There was a problem hiding this comment.
Pull request overview
Adds a PowerShell-based DSC secret extension that retrieves values via the Microsoft.PowerShell.SecretManagement module, plus a Pester test validating end-to-end secret resolution through dsc config get.
Changes:
- Added a SecretManagement-backed secret extension manifest and retrieval script (
pwsh+Get-Secret). - Added a Pester test that installs SecretManagement/SecretStore and validates
[secret('...')]in a config. - Added
copy_files.txtto include extension files during packaging (though packaging wiring appears incomplete).
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
| extensions/powershell/secret/microsoft.powershell.secret.tests.ps1 | Adds an end-to-end Pester test that provisions SecretStore and validates DSC secret expansion. |
| extensions/powershell/secret/microsoft.powershell.secret.ps1 | Implements the secret lookup script using Get-Secret -AsPlainText. |
| extensions/powershell/secret/microsoft.powershell.dsc.extension.json | Declares the extension manifest for Microsoft.PowerShell/SecretManagement. |
| extensions/powershell/secret/copy_files.txt | Declares files to copy into build outputs for this extension. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
| } | ||
|
|
||
| $env:PSModulePath += [System.IO.Path]::PathSeparator + $TestDrive |
There was a problem hiding this comment.
$env:PSModulePath is modified but never restored. This can leak state into later Pester suites in the same run; please capture the original value in BeforeAll and restore it in AfterAll (similar to how other tests restore $env:PATH).
| # Instead of doing it in the BeforeAll block, reset the store here as we know we are running in the CI | ||
| Reset-SecretStore -Password (ConvertTo-SecureString -AsPlainText -String 'P@ssw0rd' -Force) -Force | ||
| Register-SecretVault -Name 'VaultA' -ModuleName 'Microsoft.PowerShell.SecretStore' -DefaultVault |
There was a problem hiding this comment.
Reset-SecretStore resets the current user’s SecretStore and can delete/overwrite existing secrets. If this suite is intended for CI-only execution, please ensure it is skipped locally; otherwise, consider using an isolated/temporary store configuration so running Invoke-Pester doesn’t destroy a developer’s SecretStore state.
| $secret = Get-Secret @secretParams -ErrorAction Ignore | ||
|
|
||
| Write-Output $secret |
There was a problem hiding this comment.
If Get-Secret isn’t available (module missing) or Get-Secret fails for reasons other than “not found”, this script silently produces no output due to the Get-Command guard and -ErrorAction Ignore. That can surface as a misleading “Secret '' not found” from DSC. Consider emitting a clear error to stderr and returning a non-zero exit code when prerequisites are missing or when Get-Secret errors unexpectedly, while still returning no output for the “secret not found” case.
| $secret = Get-Secret @secretParams -ErrorAction Ignore | |
| Write-Output $secret | |
| try { | |
| $secret = Get-Secret @secretParams -ErrorAction Stop | |
| Write-Output $secret | |
| } | |
| catch { | |
| $errorRecord = $_ | |
| $exceptionTypeName = $errorRecord.Exception.GetType().Name | |
| $fullyQualifiedErrorId = $errorRecord.FullyQualifiedErrorId | |
| # Treat "secret not found" as a non-error and return no output to preserve existing behavior. | |
| if ($exceptionTypeName -eq 'SecretNotFoundException' -or | |
| $fullyQualifiedErrorId -like '*SecretNotFound*') { | |
| return | |
| } | |
| Write-Error -Message ("Failed to retrieve secret '{0}': {1}" -f $Name, $errorRecord.Exception.Message) | |
| exit 1 | |
| } | |
| } | |
| else { | |
| Write-Error -Message "Required cmdlet 'Get-Secret' was not found. Ensure the Microsoft.PowerShell.SecretManagement module is installed and imported." | |
| exit 1 |
| $secretParams['Vault'] = $Vault | ||
| } | ||
|
|
||
| $secret = Get-Secret @secretParams -ErrorAction Ignore |
There was a problem hiding this comment.
Trailing whitespace after -ErrorAction Ignore can cause noisy diffs/lint failures in some pipelines; please remove the extra space at end of line.
| $secret = Get-Secret @secretParams -ErrorAction Ignore | |
| $secret = Get-Secret @secretParams -ErrorAction Ignore |
| @@ -0,0 +1,2 @@ | |||
| microsoft.powershell.secret.ps1 | |||
| microsoft.powershell.dsc.extension.json | |||
There was a problem hiding this comment.
Adding copy_files.txt suggests these files should be included in build/package outputs, but packaging.ps1 currently only builds/copies extensions/appx under the extensions/ tree. Without adding extensions/powershell/secret to the build/packaging project list (or otherwise wiring this directory into packaging), these files likely won’t ship in produced artifacts.
| microsoft.powershell.dsc.extension.json |
| BeforeDiscovery { | ||
| $runningInCI = $false | ||
| } |
There was a problem hiding this comment.
$runningInCI is hard-coded to $false, but the Describe block uses -Skip:($runningInCI), so these tests will always run (including on developer machines). Because the test later calls Reset-SecretStore, this can wipe a developer’s existing SecretStore; please detect CI via environment variables (e.g., GITHUB_RUN_ID/TF_BUILD) and gate the suite appropriately (or remove the destructive calls outside CI).
| foreach ($module in $FullyQualifiedName) { | ||
| if (-not (Get-Module -ListAvailable -FullyQualifiedName $module)) { | ||
| Save-PSResource -Name $module.ModuleName -Version $module.ModuleVersion -Path $TestDrive -Repository PSGallery -TrustRepository |
There was a problem hiding this comment.
This test downloads modules directly from PSGallery. In this repo’s CI, packaging.ps1 switches to the internal CFS mirror when $env:TF_BUILD is set; using PSGallery here can fail in ADO/offline environments. Consider selecting the repository dynamically (and registering CFS when needed) to match the build/test pipeline.
| foreach ($module in $FullyQualifiedName) { | |
| if (-not (Get-Module -ListAvailable -FullyQualifiedName $module)) { | |
| Save-PSResource -Name $module.ModuleName -Version $module.ModuleVersion -Path $TestDrive -Repository PSGallery -TrustRepository | |
| # Select repository dynamically to align with CI configuration | |
| $repositoryName = 'PSGallery' | |
| if ($env:TF_BUILD -and (Get-Command -Name Get-PSResourceRepository -ErrorAction SilentlyContinue)) { | |
| $cfsRepo = Get-PSResourceRepository -Name 'CFS' -ErrorAction SilentlyContinue | |
| if ($null -ne $cfsRepo) { | |
| $repositoryName = 'CFS' | |
| } | |
| } | |
| foreach ($module in $FullyQualifiedName) { | |
| if (-not (Get-Module -ListAvailable -FullyQualifiedName $module)) { | |
| Save-PSResource -Name $module.ModuleName -Version $module.ModuleVersion -Path $TestDrive -Repository $repositoryName -TrustRepository |
PR Summary
This pull request adds the
Microsoft.PowerShell.SecretManagementextension.PR Context